package data.datas;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import network.e2e.FrameReader;
import network.e2e.FrameWriter;
import stalker.Stalker;
import stalker.StalkerParser;
import data.Data;
import data.DataParser;
import data.content.Content;
import data.content.ContentSentence;
import data.content.ContentStalkerJoin;
import data.content.visitor.UpdateNoHistoryDatasContentVisitor;
import data.visitor.PasserDataVisitor;

public class NoHistoryDatas extends Datas {
  
  /**
   * Memorizes the users of this room
   * <p>
   * Rep invariant: sorted according to creation time (last one = newest)
   */
  private final Set<Stalker> stalkers_;
  private final Lock stalkers_lock_ = new ReentrantLock(); 
  private Data last_data_;
  private final Lock last_data_lock_ = new ReentrantLock();
  
  private void checkRep() {
    assert (stalkers_ != null) :
      "NoHistoryDatas: fields must not be null";
  }
  
  /**
   * Creates an empty collection of datas
   */
  public NoHistoryDatas() {
    stalkers_ = new HashSet<Stalker>();
    last_data_ = new Data(Stalker.DEFAULT, new ContentSentence("None"));
    checkRep();
  }
  
  /**
   * Reads a datas from the net and create it
   * @throws IOException 
   * @requires reader not null
   */
  public NoHistoryDatas(FrameReader reader) throws IOException {
    this();
    assert (reader != null);
    
    long count = reader.readLong();
    for (int i = 0; i < count; ++i) {
      stalkers_.add(StalkerParser.retrieve(reader));
    }
    
    last_data_ = DataParser.retrieve(reader);
    
    checkRep();
  }
  
  /**
   * Sent this through the net
   * @requires writer not null
   * @param writer the net
   */
  @Override
  public void send(FrameWriter writer) throws IOException {
    assert writer != null;
    stalkers_lock_.lock();
    writer.writeLong((long)stalkers_.size());
    for (Stalker stalker : stalkers_) {
      StalkerParser.send(stalker, writer);
    }
    stalkers_lock_.unlock();
    
    last_data_lock_.lock();
    DataParser.send(last_data_, writer);
    last_data_lock_.unlock();
  }
  
  /**
   * Mutate this by appending a data to the end of this collection
   * @requires all parameters not null
   */
  @Override
  public void addData(Stalker creator, Content content) {
    assert (creator != null && content != null);
    insertData(new Data(creator, content));
    checkRep();
  }
  
  /**
   * Retrieves the latest data in this collection
   * @requires collection is not empty
   * @return The last data added to the collection
   */
  @Override
  public Data retrieveLatestData() {
    return last_data_;
  }
  
  /**
   * Appends a data to the appropriate location
   * @requires data not null
   * @param data the data to be inserted
   */
  @Override
  public void insertData(Data data) {
    last_data_lock_.lock();
    last_data_ = data;
    last_data_lock_.unlock();
    
    data.accept(new PasserDataVisitor<Void>(new UpdateNoHistoryDatasContentVisitor(this)));
  }
  
  /**
   * Checks whether the collection contains no data
   * @return true iff the collection contains no data
   */
  @Override
  public boolean isEmpty() {
    return false;
  }
  
  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof NoHistoryDatas)) {
      return false;
    }
    
    NoHistoryDatas that = (NoHistoryDatas)obj;
    
    return this.retrieveLatestData().equals(that.retrieveLatestData()) &&
           this.retrieveAllDatas().equals(that.retrieveAllDatas());
  }

  @Override
  public List<Data> retrieveAllDatas() {
    List<Data> datas_ = new ArrayList<Data>();
    stalkers_lock_.lock();
    for (Stalker stalker : stalkers_) {
      datas_.add(new Data(stalker, new ContentStalkerJoin(stalker)));
    }
    stalkers_lock_.unlock();
    return datas_;
  }
  
  public void join(Stalker stalker) {
    stalkers_lock_.lock();
    stalkers_.add(stalker);
    stalkers_lock_.unlock();
  }
  
  public void leave(Stalker stalker) {
    stalkers_lock_.lock();
    stalkers_.remove(stalker);
    stalkers_lock_.unlock();
  }
  
}
